#include <mylib/driver/soft_i2c.h>
#include <mylib/driver/gpio.h>

///////////////////////////////////////////////////////////////////////////////////////////////////
// 修改为针对平台的不同实现
// 设置SCL为高电平
static inline void soft_i2c_scl_high(soft_i2c_t* soft_i2c)
{
    gpio_set_value(soft_i2c->scl_gpio, GPIO_LEVEL_HIGH);
}
// 设置SCL为低电平
static inline void soft_i2c_scl_low(soft_i2c_t* soft_i2c)
{
    gpio_set_value(soft_i2c->scl_gpio, GPIO_LEVEL_LOW);
}
// 设置SDA为高电平
static inline void soft_i2c_sda_high(soft_i2c_t* soft_i2c)
{
    gpio_set_value(soft_i2c->sda_gpio, GPIO_LEVEL_HIGH);
}
// 设置SDA为低电平
static inline void soft_i2c_sda_low(soft_i2c_t* soft_i2c)
{
    gpio_set_value(soft_i2c->sda_gpio, GPIO_LEVEL_LOW);
}
// 设置SDA为输出模式
static inline void soft_i2c_sda_out(soft_i2c_t* soft_i2c)
{
    gpio_direction_output(soft_i2c->sda_gpio);
}
// 设置SDA为输入模式
static inline void soft_i2c_sda_in(soft_i2c_t* soft_i2c)
{
    gpio_direction_input(soft_i2c->sda_gpio);
}
// 读取SDA的电平
static inline uint8_t soft_i2c_sda_read(soft_i2c_t* soft_i2c)
{
    return gpio_get_value(soft_i2c->sda_gpio);
}
// I2C总线位延迟，最快400KHz
static void soft_i2c_delay(void)
{
    volatile uint8_t i;

    /*　
        下面的时间是通过安富莱AX-Pro逻辑分析仪测试得到的。
        CPU主频72MHz时，在内部Flash运行, MDK工程不优化
        循环次数为10时，SCL频率 = 205KHz
        循环次数为7时，SCL频率 = 347KHz， SCL高电平时间1.5us，SCL低电平时间2.87us
        循环次数为5时，SCL频率 = 421KHz， SCL高电平时间1.25us，SCL低电平时间2.375us

    IAR工程编译效率高，不能设置为7
    */
    for (i = 0; i < 10; i++);

#if 0
    for (i = 0; i < 5; i++);
#endif
}

////////////////////////////////////////////////////////////////////////////////////////////////////

// 发起I2C总开始信号
void soft_i2c_start(soft_i2c_t* soft_i2c)
{
    // 当SCL高电平时，SDA出现一个下跳沿表示I2C总线启动信号，如图
    //     _____
    // SDA      \_____________
    //     __________
    // SCL           \________
    //

    soft_i2c_sda_out(soft_i2c);
    soft_i2c_sda_high(soft_i2c);
    soft_i2c_scl_high(soft_i2c);
    soft_i2c_delay();
    soft_i2c_sda_low(soft_i2c);
    soft_i2c_delay();
    soft_i2c_scl_low(soft_i2c);
    soft_i2c_delay();
}

// 发起I2C总线停止信号
void soft_i2c_stop(soft_i2c_t* soft_i2c)
{
    // 当SCL高电平时，SDA出现一个上跳沿表示I2C总线停止信号，如图
    //                _______
    // SDA __________/
    //           ____________
    // SCL _____/
    //

    soft_i2c_sda_out(soft_i2c);
    soft_i2c_sda_low(soft_i2c);
    soft_i2c_scl_high(soft_i2c);
    soft_i2c_delay();
    soft_i2c_sda_high(soft_i2c);
    soft_i2c_delay();
}

// 向I2C总线设备发送8bit数据
void soft_i2c_send_byte(soft_i2c_t* soft_i2c, uint8_t byte)
{
    uint8_t i;
    soft_i2c_sda_out(soft_i2c);
    soft_i2c_scl_low(soft_i2c);
    for (i = 0; i < 8; i++) {
        if ((byte & 0x80) >> 7) {
            soft_i2c_sda_high(soft_i2c);
        }
        else {
            soft_i2c_sda_low(soft_i2c);
        }
        byte <<= 1;
        soft_i2c_delay();
        soft_i2c_scl_high(soft_i2c);
        soft_i2c_delay();
        soft_i2c_scl_low(soft_i2c);
        soft_i2c_delay();
    }
    soft_i2c_wait_ack(soft_i2c);
}

/*
*********************************************************************************************************
*	函 数 名: i2c_ReadByte
*	功能说明: CPU从I2C总线设备读取8bit数据
*	形    参：无
*	返 回 值: 读到的数据
*********************************************************************************************************
*/
uint8_t soft_i2c_read_byte(soft_i2c_t* soft_i2c, uint8_t ack)
{
    uint8_t i, data = 0;
    for (i = 0; i < 8; i++) {
        data <<= 1;
        soft_i2c_scl_high(soft_i2c);
        soft_i2c_delay();
        if (soft_i2c_sda_read(soft_i2c)) {
            data++;
        }
        soft_i2c_scl_low(soft_i2c);
        soft_i2c_delay();
    }
    return data;
}

/*
*********************************************************************************************************
*	函 数 名: i2c_WaitAck
*	功能说明: CPU产生一个时钟，并读取器件的ACK应答信号
*	形    参：无
*	返 回 值: 返回0表示正确应答，1表示无器件响应
*********************************************************************************************************
*/
uint8_t soft_i2c_wait_ack(soft_i2c_t* soft_i2c)
{
    uint32_t timeout = 0;
    soft_i2c_sda_high(soft_i2c);
    soft_i2c_sda_in(soft_i2c);
    soft_i2c_delay();
    soft_i2c_scl_high(soft_i2c);
    soft_i2c_delay();
    while (soft_i2c_sda_read(soft_i2c)) {
        timeout++;
        if (timeout > 100)   // 2000
        {
            soft_i2c_stop(soft_i2c);
            return 1;
        }
    }
    soft_i2c_scl_low(soft_i2c);
    soft_i2c_delay();
    return 0;
}

/*
*********************************************************************************************************
*	函 数 名: i2c_Ack
*	功能说明: CPU产生一个ACK信号
*	形    参：无
*	返 回 值: 无
*********************************************************************************************************
*/
void soft_i2c_ack(soft_i2c_t* soft_i2c)
{
    /*           ____
     *SCL ______/    \______
     *    ____         _____
     *SDA     \_______/
     */
    soft_i2c_scl_low(soft_i2c);
    soft_i2c_sda_out(soft_i2c);
    soft_i2c_sda_high(soft_i2c);
    soft_i2c_delay();
    soft_i2c_sda_low(soft_i2c);
    soft_i2c_delay();
    soft_i2c_scl_high(soft_i2c);
    soft_i2c_delay();
    soft_i2c_scl_low(soft_i2c);
    soft_i2c_delay();
    soft_i2c_sda_high(soft_i2c);
}

/*
*********************************************************************************************************
*	函 数 名: i2c_NAck
*	功能说明: CPU产生1个NACK信号
*	形    参：无
*	返 回 值: 无
*********************************************************************************************************
*/
void soft_i2c_nack(soft_i2c_t* soft_i2c)
{
    /*           ____
     *SCL ______/    \______
     *    __________________
     *SDA
     */
    soft_i2c_scl_low(soft_i2c);
    soft_i2c_sda_out(soft_i2c);
    soft_i2c_sda_high(soft_i2c);
    soft_i2c_delay();
    soft_i2c_scl_high(soft_i2c);
    soft_i2c_delay();
    soft_i2c_scl_low(soft_i2c);
    soft_i2c_delay();
}

// 初始化I2C
void soft_i2c_init(soft_i2c_t* soft_i2c)
{
    // 给一个停止信号, 复位I2C总线上的所有设备到待机模式
    soft_i2c_stop(soft_i2c);
}

/*
*********************************************************************************************************
*	函 数 名: i2c_CheckDevice
*	功能说明: 检测I2C总线设备，CPU向发送设备地址，然后读取设备应答来判断该设备是否存在
*	形    参：_Address：设备的I2C总线地址
*	返 回 值: 返回值 0 表示正确， 返回1表示未探测到
*********************************************************************************************************
*/
uint8_t soft_i2c_check_device(soft_i2c_t* soft_i2c, uint8_t address)
{

    uint8_t ack;

    soft_i2c_init(soft_i2c); /* 配置GPIO */

    soft_i2c_start(soft_i2c); /* 发送启动信号 */

    /* 发送设备地址+读写控制bit（0 = w， 1 = r) bit7 先传 */
    soft_i2c_send_byte(soft_i2c, address | SOFT_I2C_WR);
    ack = soft_i2c_wait_ack(soft_i2c); /* 检测设备的ACK应答 */

    soft_i2c_stop(soft_i2c); /* 发送停止信号 */

    return ack;
}
